<!--
@license
Copyright 2016 The TensorFlow Authors. All Rights Reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->

<link rel="import" href="../iron-collapse/iron-collapse.html">
<link rel="import" href="../iron-list/iron-list.html">
<link rel="import" href="../polymer/polymer.html">
<link rel="import" href="../paper-icon-button/paper-icon-button.html">
<link rel="import" href="../paper-item/all-imports.html">
<link rel="import" href="../tf-graph-common/tf-graph-common.html">
<link rel="import" href="../tf-graph/tf-graph-icon.html">
<link rel="import" href="tf-node-list-item.html">

<dom-module id="tf-node-info">
  <style>
  .sub-list-group {
    font-weight: 500;
    font-size: 12pt;
    padding-bottom: 8px;
    width: 100%;
  }

  .sub-list {
    max-height: 300px;
    overflow-y: scroll;
  }

  .attr-left {
    float: left;
    width: 30%;
    word-wrap: break-word;
    color: #565656;
    font-size: 11pt;
    font-weight: 400;
  }

  .attr-right {
    margin-left: 30%;
    word-wrap: break-word;
    color: #565656;
    font-weight: 400;
  }

  .sub-list-table {
    display: table;
    width: 100%;
  }

  .sub-list-table-row {
    display: table-row;
  }

  .sub-list-table-row .sub-list-table-cell:last-child {
    text-align: right;
  }

  .sub-list-table-cell {
    color: #565656;
    display: table-cell;
    font-size: 11pt;
    font-weight: 400;
    max-width: 200px;
    padding: 0 4px;
  }

  paper-item {
    padding: 0;
    background: #e9e9e9;
  }

  paper-item-body[two-line] {
    min-height: 0;
    padding: 8px 12px 4px;
  }

  .expandedInfo {
    padding: 8px 12px;
  }

  .controlDeps {
    padding: 0 0 0 8px;
  }

  .node-name {
    white-space: normal;
    word-wrap: break-word;
    font-size: 14pt;
    font-weight: 500;
  }

  .node-icon {
    float: right;
  }

  .subtitle {
    font-size: 12pt;
    color: #5e5e5e;
  }

  .controlLine {
    font-size: 11pt;
    font-weight: 400;
  }

  .toggle-button {
    float: right;
    max-height: 20px;
    max-width: 20px;
    padding: 0;
  }

  .control-toggle-button {
    float: left;
    max-height: 20px;
    max-width: 20px;
    padding: 0;
  }

  .toggle-include-group {
    padding-top: 4px;
  }

  .toggle-include {
    margin: 5px 6px;
    text-transform: none;
    padding: 4px 6px;
    font-size: 10pt;
    background-color: #fafafa;
    color: #666;
  }

  .toggle-include:hover {
    background-color: var(--google-yellow-100);
  }

  .non-control-list-item {
    padding-left: 10px;
  }
  </style>
  <template>
    <paper-item>
      <paper-item-body two-line>
        <div>
          <paper-icon-button
            icon="{{_getToggleIcon(_expanded)}}"
            on-click="_toggleExpanded"
            class="toggle-button">
          </paper-icon-button>
          <div class="node-name" id="nodetitle"></div>
        </div>
        <div secondary>
          <tf-graph-icon class="node-icon" node="[[_node]]"
              render-info="[[_getRenderInfo(nodeName, renderHierarchy)]]"
              color-by="[[colorBy]]"
              template-index="[[_templateIndex]]"
              ></tf-graph-icon>
          <template is="dom-if" if="{{_node.op}}">
            <div class="subtitle">
              Operation:
              <span>[[_node.op]]</span>
            </div>
          </template>
          <template is="dom-if" if="{{_node.metagraph}}">
            <div class="subtitle">
              Subgraph:
              <span>[[_node.cardinality]]</span> nodes
            </div>
          </template>
        </div>
      </paper-item-body>
    </paper-item>
    <iron-collapse opened="{{_expanded}}">
    <template is="dom-if" if="{{_expanded}}" restamp="true">
      <div class="expandedInfo">
        <div class="sub-list-group attributes">
          Attributes
          (<span>[[_attributes.length]]</span>)
          <iron-list class="sub-list" id ="attributesList"
                    items="[[_attributes]]">
            <template>
              <div>
                <div class="attr-left">[[item.key]]</div>
                <div class="attr-right">[[item.value]]</div>
              </div>
            </template>
          </iron-list>
        </div>

        <template is="dom-if" if="{{_device}}">
          <div class="sub-list-group device">
            <div class="attr-left">Device</div>
            <div class="attr-right">[[_device]]</div>
          </div>
        </template>

        <div class="sub-list-group predecessors">
          Inputs
          (<span>[[_totalPredecessors]]</span>)
          <iron-list class="sub-list" id ="inputsList"
                    items="[[_predecessors.regular]]">
            <template>
              <tf-node-list-item
                  class="non-control-list-item"
                  card-node="[[_node]]"
                  item-node="[[item.node]]"
                  edge-label="[[item.edgeLabel]]"
                  item-render-info="[[item.renderInfo]]"
                  name="[[item.name]]"
                  item-type="predecessors"
                  color-by="[[colorBy]]"
                  template-index="[[_templateIndex]]">
              </tf-node-list-item>
            </template>
          </iron-list>
          <template is="dom-if" if="[[_predecessors.control.length]]">
            <div class="controlDeps">
              <div class="controlLine">
                <paper-icon-button
                  icon="{{_getToggleIcon(_openedControlPred)}}"
                  on-click="_toggleControlPred"
                  class="control-toggle-button">
                </paper-icon-button>
                Control dependencies
              </div>
              <iron-collapse opened="{{_openedControlPred}}" no-animation>
                <template is="dom-if" if="{{_openedControlPred}}" restamp="true">
                  <iron-list class="sub-list" items="[[_predecessors.control]]">
                    <template>
                      <tf-node-list-item
                          card-node="[[_node]]"
                          item-node="[[item.node]]"
                          item-render-info="[[item.renderInfo]]"
                          name="[[item.name]]"
                          item-type="predecessors"
                          color-by="[[colorBy]]"
                          template-index="[[_templateIndex]]">
                      </tf-node-list-item>
                    </template>
                  </iron-list>
                </template>
              </iron-collapse>
            </div>
          </template>
        </div>

        <div class="sub-list-group successors">
          Outputs
          (<span>[[_totalSuccessors]]</span>)
          <iron-list class="sub-list" id ="outputsList"
                    items="[[_successors.regular]]">
            <template>
              <tf-node-list-item
                  class="non-control-list-item"
                  card-node="[[_node]]"
                  item-node="[[item.node]]"
                  edge-label="[[item.edgeLabel]]"
                  item-render-info="[[item.renderInfo]]"
                  name="[[item.name]]"
                  item-type="successor"
                  color-by="[[colorBy]]"
                  template-index="[[_templateIndex]]">
              </tf-node-list-item>
            </template>
          </iron-list>
          <template is="dom-if" if="[[_successors.control.length]]">
            <div class="controlDeps">
              <div class="controlLine">
                <paper-icon-button
                  icon="{{_getToggleIcon(_openedControlSucc)}}"
                  on-click="_toggleControlSucc"
                  class="control-toggle-button">
                </paper-icon-button>
                Control dependencies
              </div>
              <iron-collapse opened="{{_openedControlSucc}}" no-animation>
                <template is="dom-if" if="{{_openedControlSucc}}" restamp="true">
                  <iron-list class="sub-list" items="[[_successors.control]]">
                    <template>
                      <tf-node-list-item
                          card-node="[[_node]]"
                          item-node="[[item.node]]"
                          item-render-info="[[item.renderInfo]]"
                          name="[[item.name]]"
                          item-type="successors"
                          color-by="[[colorBy]]"
                          template-index="[[_templateIndex]]">
                      </tf-node-list-item>
                    </template>
                  </iron-list>
                </template>
              </iron-collapse>
            </div>
          </template>
        </div>
        <template is="dom-if" if="{{_hasDisplayableNodeStats}}">
          <div class="sub-list-group node-stats">
            Node Stats
            <div class="sub-list-table">
              <template is="dom-if" if="{{_nodeStats.totalBytes}}">
                <div class="sub-list-table-row">
                  <div class="sub-list-table-cell">Memory</div>
                  <div class="sub-list-table-cell">[[_nodeStatsFormattedBytes]]</div>
                </div>
              </template>
              <template is="dom-if" if="{{_nodeStats.totalMicros}}">
                <div class="sub-list-table-row">
                  <div class="sub-list-table-cell">Compute Time</div>
                  <div class="sub-list-table-cell">[[_nodeStatsFormattedComputeTime]]</div>
                </div>
              </template>
              <template is="dom-if" if="{{_nodeStats.outputSize}}">
                <div class="sub-list-table-row">
                  <div class="sub-list-table-cell">Tensor Output Sizes</div>
                  <div class="sub-list-table-cell">
                    <template is="dom-repeat" items="{{_nodeStatsFormattedOutputSizes}}">
                      [[item]] <br/>
                    </template>
                  </div>
                </div>
              </template>
            </div>
          </div>
        </template>
        <div class="toggle-include-group">
          <paper-button raised class="toggle-include" on-click="_toggleInclude">
            <span>[[_auxButtonText]]</span>
          </paper-button>
        </div>
        <template is="dom-if" if="{{_isInSeries(_node)}}">
          <div class="toggle-include-group">
            <paper-button raised class="toggle-include" on-click="_toggleGroup">
              <span>[[_groupButtonText]]</span>
            </paper-button>
          </div>
        </template>
      </div>
    </template>
    </iron-collapse>
  </template>

  <script>
    (function() {
      Polymer({
        is: 'tf-node-info',

        properties: {
          nodeName: String,
          graphHierarchy: Object,
          renderHierarchy: Object,
          /** What to color the nodes by (compute time, memory, device etc.) */
          colorBy: String,
          _templateIndex: {
            type: Function,
            computed: '_getTemplateIndex(graphHierarchy)'
          },
          _node: {
            type: Object,
            computed: '_getNode(nodeName, graphHierarchy)',
            observer: '_resetState'
          },
          _nodeStats: {
            type: Object,
            computed: '_getNodeStats(nodeName, graphHierarchy)',
            observer: '_resetState'
          },
          _hasDisplayableNodeStats: {
            type: Object,
            computed: '_getHasDisplayableNodeStats(_nodeStats)',
          },
          _nodeStatsFormattedBytes: {
            type: String,
            computed: '_getNodeStatsFormattedBytes(_nodeStats)',
          },
          _nodeStatsFormattedComputeTime: {
            type: String,
            computed: '_getNodeStatsFormattedComputeTime(_nodeStats)',
          },
          _nodeStatsFormattedOutputSizes: {
            type: Array,
            computed: '_getNodeStatsFormattedOutputSizes(_nodeStats)',
          },
          // The enum value of the include property of the selected node.
          nodeInclude: {
            type: Number,
            observer: '_nodeIncludeStateChanged'
          },
          _attributes: {
            type: Array,
            computed: '_getAttributes(_node)'
          },
          _device: {
            type: String,
            computed: '_getDevice(_node)'
          },
          _successors: {
            type: Object,
            computed: '_getSuccessors(_node, graphHierarchy)'
          },
          _predecessors: {
            type: Object,
            computed: '_getPredecessors(_node, graphHierarchy)'
          },
          _subnodes: {
            type: Array,
            computed: '_getSubnodes(_node)'
          },
          _expanded: {
            type: Boolean,
            value: true
          },
          _totalPredecessors: {
            type: Number,
            computed: '_getTotalPred(_predecessors)'
          },
          _totalSuccessors: {
            type: Number,
            computed: '_getTotalSucc(_successors)'
          },
          _openedControlPred: {
            type: Boolean,
            value: false
          },
          _openedControlSucc: {
            type: Boolean,
            value: false
          },
          _auxButtonText: String,
          _groupButtonText: String
        },
        expandNode: function() {
          this.fire('_node.expand', this.node);
        },
        _getTemplateIndex: function(graphHierarchy) {
          return graphHierarchy.getTemplateIndex();
        },
        _getNode: function(nodeName, graphHierarchy) {
          return graphHierarchy.node(nodeName);
        },
        _getNodeStats: function(nodeName, graphHierarchy) {
          var node = this._getNode(nodeName, graphHierarchy);
          if (node) {
            return node.stats;
          }
          return null;
        },
        _getHasDisplayableNodeStats: function(stats) {
          return tf.graph.util.hasDisplayableNodeStats(stats);
        },
        _getNodeStatsFormattedBytes: function(stats) {
          if (!stats || !stats.totalBytes) {
            return;
          }

          return tf.graph.util.convertUnitsToHumanReadable(
              stats.totalBytes, tf.graph.util.MEMORY_UNITS);
        },
        _getNodeStatsFormattedComputeTime: function(stats) {
          if (!stats || !stats.totalMicros) {
            return;
          }

          return tf.graph.util.convertUnitsToHumanReadable(
              stats.totalMicros, tf.graph.util.TIME_UNITS);
        },
        _getNodeStatsFormattedOutputSizes: function(stats) {
          if (!stats || !stats.outputSize || !stats.outputSize.length) {
            return;
          }

          return _.map(stats.outputSize, function(shape) {
            if (shape.length === 0) {
              return "scalar";
            }
            return "[" + shape.join(", ") + "]";
          });
        },
        _getPrintableHTMLNodeName: function(nodeName) {
          // Insert an optional line break before each slash so that
          // long node names wrap cleanly at path boundaries.
          return (nodeName || '').replace(/\//g, '<wbr>/');
        },
        _getRenderInfo: function(nodeName, renderHierarchy) {
          return this.renderHierarchy.getOrCreateRenderNodeByName(nodeName);
        },
        _getAttributes: function(node) {
          this.async(this._resizeList.bind(this, "#attributesList"));
          if (!node || !node.attr) {
            return [];
          }
          var attrs = [];
          _.each(node.attr, function(entry) {
            // Unpack the "too large" attributes into separate attributes
            // in the info card, with values "too large to show".
            if (entry.key === tf.graph.LARGE_ATTRS_KEY) {
              attrs = attrs.concat(entry.value.list.s.map(function(key) {
                return {key: key, value: "Too large to show..."};
              }));
            } else {
              attrs.push({
                key: entry.key,
                value: JSON.stringify(entry.value)
              });
            }
          });
          return attrs;
        },
        _getDevice: function(node) {
          return node ? node.device : null;
        },
        _getSuccessors: function(node, hierarchy) {
          this.async(this._resizeList.bind(this, "#inputsList"));
          if (!node) {
            return {regular: [], control: []}
          }
          return this._convertEdgeListToEdgeInfoList(
            hierarchy.getSuccessors(node.name), false, node.isGroupNode);
        },
        _getPredecessors: function(node, hierarchy) {
          this.async(this._resizeList.bind(this, "#outputsList"));
          if (!node) {
            return {regular: [], control: []}
          }
          return this._convertEdgeListToEdgeInfoList(
            hierarchy.getPredecessors(node.name), true, node.isGroupNode);
        },
        _convertEdgeListToEdgeInfoList: function(list, isPredecessor, isGroupNode) {

          /**
           * Unpacks the metaedge into a list of base edge information
           * that can be rendered.
           */
          var unpackMetaedge = function(metaedge) {
            return _.map(metaedge.baseEdgeList, function(baseEdge) {
              name = isPredecessor ? baseEdge.v : baseEdge.w;
              return {
                name: name,
                node: this._getNode(name, this.graphHierarchy),
                edgeLabel: tf.graph.scene.edge.getLabelForBaseEdge(baseEdge,
                    this.renderHierarchy),
                renderInfo: this._getRenderInfo(name, this.renderHierarchy)
              };
            }, this);
          }.bind(this);

          /**
           * Converts a list of metaedges to a list of edge information
           * that can be rendered.
           */
          var toEdgeInfoList = function(edges) {
            var edgeInfoList = [];
            _.each(edges, function(metaedge) {
              var name = isPredecessor ? metaedge.v : metaedge.w;
              // Enumerate all the base edges if the node is an OpNode, or the
              // metaedge has only 1 edge in it.
              if (!isGroupNode || metaedge.baseEdgeList.length == 1) {
                edgeInfoList = edgeInfoList.concat(unpackMetaedge(metaedge));
              } else {
                edgeInfoList.push({
                  name: name,
                  node: this._getNode(name, this.graphHierarchy),
                  edgeLabel: tf.graph.scene.edge.getLabelForEdge(metaedge,
                      this.renderHierarchy),
                  renderInfo: this._getRenderInfo(name, this.renderHierarchy)
                });
              }
            }, this);
            return edgeInfoList;
          }.bind(this);

          return {
            regular: toEdgeInfoList(list.regular),
            control: toEdgeInfoList(list.control)
          };
        },
        _getSubnodes: function(node) {
          return node && node.metagraph ? node.metagraph.nodes() : null;
        },
        _getTotalPred: function(predecessors) {
          return predecessors.regular.length + predecessors.control.length;
        },
        _getTotalSucc: function(successors) {
          return successors.regular.length + successors.control.length;
        },
        _toggleControlPred: function() {
          this._openedControlPred = !this._openedControlPred;
        },
        _toggleControlSucc: function() {
          this._openedControlSucc = !this._openedControlSucc;
        },
        _toggleExpanded: function() {
          this._expanded = !this._expanded;
        },
        _getToggleIcon: function(expanded) {
          return expanded ? "expand-less" : "expand-more";
        },
        _resetState: function() {
          this._openedControlPred = false;
          this._openedControlSucc = false;

          this.set("_groupButtonText",
            tf.graph.scene.node.getGroupSettingLabel(this._node));

          if (this._node) {
            Polymer.dom(this.$.nodetitle).innerHTML =
              this._getPrintableHTMLNodeName(this._node.name);
          }
        },
        _resizeList: function(selector) {
          var list = document.querySelector(selector);
          if (list) {
            list.fire('iron-resize');
          }
        },
        _toggleInclude: function() {
          var graphElem = document.querySelector("#graph");
          graphElem.fire("node-toggle-extract", { name: this.nodeName });
          var graphBoardElem = document.querySelector("#graphboard");
          graphBoardElem.fire("node-toggle-extract");
        },
        _nodeIncludeStateChanged: function(include, oldInclude) {
          this.set("_auxButtonText",
            tf.graph.getIncludeNodeButtonString(include));
        },
        _toggleGroup: function() {
          var graphElem = document.querySelector("#graph");
          var seriesName = tf.graph.scene.node.getSeriesName(this._node);
          graphElem.fire("node-toggle-seriesgroup", { name: seriesName });
        },
        _isInSeries: function(node) {
          return tf.graph.scene.node.canBeInSeries(node);
        }
      });
    })();
  </script>
</dom-module>
